using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using Pinwheel.Toolkits;

namespace Pinwheel.LSerialize
{
    public unsafe partial struct LStreamWriter
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteArrayField<T>(LSerializeContext* context, T[] value) where T : IEquatable<T>
        {
            if (null == value || value.Length == 0)
            {
                // 递增Tag 
                context->IncreaseTag();
                return;
            }
            
            // 如果不存在引用，递增RefCount
            if (!WriteManagedRefId<T[]>(context, value.GetHashCode(), out var refId))
            {
                if (null != context->RefHeader) context->RefHeader->RefCount++;
                WriteNextDepthBegin<T[]>(context);
                WriteArrayValue<T>(context, value, ref refId);
                WriteNextDepthEnd<T[]>(context);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteArrayValue<T>(LSerializeContext* context, T[] value, ref long refId) where T : IEquatable<T>
        {
            WriteValueBegin(context, ref refId);
            context->SetValidFieldCount(value.Length);
            bool isUnmanaged = !RuntimeHelpers.IsReferenceOrContainsReferences<T>();
            if (isUnmanaged)
            {
                Seek(sizeof(ReferenceHeader), SeekOrigin.Current);
                UnsafeWriteUnmanagedArray<T>(ref value);   
                context->RefHeader->ValCount = (ushort) value.Length;
            }
            else
            {
                for (int i = 0; i < value.Length; i++)
                    WriteManagedField(context, ref value[i]); 
            }
            WriteValueEnd(context);
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteListField<T>(LSerializeContext* context, List<T> value) where T : IEquatable<T>
        {
            Profiler.Start("LStreamWriter.WriteListField CheckNull");
            if (null == value || value.Count == 0)
            {
                // 递增Tag 
                context->IncreaseTag();
                return;
            }
            Profiler.End();
            
            // 如果不存在引用，递增RefCount
            if (!WriteManagedRefId<List<T>>(context, value.GetHashCode(), out var refId))
            {
                // var type = typeof(T);
                // context->SetValidFieldCount(value.Count);
                // context->ResetValHeader(type.GetFieldType(), CollectionType.List);
                // context->RefHeader->ValCount = context->RefHeader->RefCount = (ushort)value.Count;
                if (null != context->RefHeader) context->RefHeader->RefCount++;
                WriteNextDepthBegin<List<T>>(context);
                WriteListValue<T>(context, value, refId);
                WriteNextDepthEnd<List<T>>(context);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteListValue<T>(LSerializeContext* context, List<T> value, long refId = 0L) where T : IEquatable<T>
        {
            WriteValueBegin(context, ref refId);
            
            context->SetValidFieldCount(value.Count);
            bool isUnmanaged = !RuntimeHelpers.IsReferenceOrContainsReferences<T>();
            if (isUnmanaged) Seek(sizeof(ReferenceHeader), SeekOrigin.Current);
            for (int i = 0; i < value.Count; i++)
            {
                var val = value[i];
                if (isUnmanaged)
                    GetFormatter<T>().Serialize(context, Ptr, ref val);
                else
                    WriteManagedField(context, ref val); 
            }
            if (isUnmanaged) context->RefHeader->ValCount = (ushort) value.Count;

            WriteValueEnd(context);
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteDictionaryField<TKey, TValue>(LSerializeContext* context, Dictionary<TKey, TValue> value) 
            where TKey : notnull, IEquatable<TKey> 
            where TValue : IEquatable<TValue> 
        {
            Profiler.Start("LStreamWriter.WriteDictionaryField CheckNull");
            if (null == value || value.Count == 0)
            {
                // 递增Tag 
                context->IncreaseTag();
                return;
            }
            Profiler.End();
            
            // 如果不存在引用，递增RefCount
            if (!WriteManagedRefId<Dictionary<TKey, TValue>>(context, value.GetHashCode(), out var refId))
            {
                if (null != context->RefHeader) context->RefHeader->RefCount++;
                WriteNextDepthBegin<Dictionary<TKey, TValue>>(context);
                WriteDictionaryValue<TKey, TValue>(context, value, refId);
                WriteNextDepthEnd<Dictionary<TKey, TValue>>(context);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteDictionaryValue<TKey, TValue>(LSerializeContext* context, Dictionary<TKey, TValue> value, long refId = 0L) 
            where TKey : notnull, IEquatable<TKey> 
            where TValue : IEquatable<TValue> 
        {
            WriteValueBegin(context, ref refId);
            
            context->SetValidFieldCount(value.Count * 2);
            bool isUnmanaged = !RuntimeHelpers.IsReferenceOrContainsReferences<TKey>() &&
                               !RuntimeHelpers.IsReferenceOrContainsReferences<TValue>();
            if (isUnmanaged) Seek(sizeof(ReferenceHeader), SeekOrigin.Current);
            foreach (var item in value)
            {
                var key = item.Key;
                var val = item.Value;
                if (isUnmanaged)
                {
                    GetFormatter<TKey>().Serialize(context, Ptr, ref key);
                    GetFormatter<TValue>().Serialize(context, Ptr, ref val);
                }
                else
                {
                    WriteManagedField<TKey>(context, ref key);
                    WriteManagedField<TValue>(context, ref val);
                }
            }

            if (isUnmanaged) context->RefHeader->ValCount = (ushort) (value.Count * 2);
            
            WriteValueEnd(context);
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool WriteInstId<T>(LSerializeContext* context, ref T[] value, out long instId) where T : IEquatable<T>
        {
            bool needRef = false;
            instId = 0L;
            if (null != value)
            {
                int hash = value.GetHashCode();
                needRef = !LSerializeExtension.Cache.TryGetReference(ref hash, out instId);
                if (needRef)
                {
                    instId = context->IncreaseRefId();
                    LSerializeExtension.Cache.CacheReference(ref hash, ref instId);
                }
                AssistValHeader(new ValHeader() {Depth = context->Depth, Tag = context->Tag});
                AssistWriteUnmanaged((ulong)instId); 
            }
            context->IncreaseTag();
            return needRef;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteArray<T>(LSerializeContext* context, ref T[] value) where T : IEquatable<T>
        {
            if (WriteInstId(context, ref value, out var instId))
            {
                HandleUnmanagedStruct(out RefHeader* header);
                var index = buffer->Index;
                context->IncreaseDepth();
                WriteUInt16((UInt16)value.Length);
                bool isUnmanaged = !RuntimeHelpers.IsReferenceOrContainsReferences<T>();
                if (isUnmanaged)
                {
                    UnsafeWriteUnmanagedArray<T>(ref value);
                }
                else
                {
                    for (int i = 0; i < value.Length; i++)
                    {
                        var v = value[i];
                        WriteRealTypeValue<T>(context, ref v);
                    } 
                }
                context->DecreaseDepth();
                header->InstId = (int)instId;
                header->Size = buffer->Index - index;
            }
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool WriteInstId<T>(LSerializeContext* context, ref List<T> value, out long instId) where T : IEquatable<T>
        {
            bool needRef = false;
            instId = 0L;
            if (null != value)
            {
                int hash = value.GetHashCode();
                needRef = !LSerializeExtension.Cache.TryGetReference(ref hash, out instId);
                if (needRef)
                {
                    instId = context->IncreaseRefId();
                    LSerializeExtension.Cache.CacheReference(ref hash, ref instId);
                }
                AssistValHeader(new ValHeader() {Depth = context->Depth, Tag = context->Tag});
                AssistWriteUnmanaged((ulong)instId); 
            }
            context->IncreaseTag();
            return needRef;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteList<T>(LSerializeContext* context, ref List<T> value) where T : IEquatable<T>
        {
            if (WriteInstId(context, ref value, out var instId))
            {
                HandleUnmanagedStruct(out RefHeader* header);
                var index = buffer->Index;
                context->IncreaseDepth();
                WriteUInt16((UInt16)value.Count);
                bool isUnmanaged = !RuntimeHelpers.IsReferenceOrContainsReferences<T>();
                for (int i = 0; i < value.Count; i++)
                {
                    var v = value[i];
                    if (isUnmanaged)
                        WriteUnmanagedStruct(context, ref v);
                    else
                        WriteRealTypeValue<T>(context, ref v);
                }
                context->DecreaseDepth();
                header->InstId = (int)instId;
                header->Size = buffer->Index - index;
            }
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool WriteInstId<TKey, TValue>(LSerializeContext* context, ref Dictionary<TKey, TValue> value, out long instId) 
            where TKey : notnull, IEquatable<TKey> 
            where TValue : IEquatable<TValue> 
        {
            bool needRef = false;
            instId = 0L;
            if (null != value)
            {
                int hash = value.GetHashCode();
                needRef = !LSerializeExtension.Cache.TryGetReference(ref hash, out instId);
                if (needRef)
                {
                    instId = context->IncreaseRefId();
                    LSerializeExtension.Cache.CacheReference(ref hash, ref instId);
                }
                AssistValHeader(new ValHeader() {Depth = context->Depth, Tag = context->Tag});
                AssistWriteUnmanaged((ulong)instId); 
            }
            context->IncreaseTag();
            return needRef;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteDictionary<TKey, TValue>(LSerializeContext* context, ref Dictionary<TKey, TValue> value, long refId = 0L) 
            where TKey : notnull, IEquatable<TKey> 
            where TValue : IEquatable<TValue> 
        {
            if (WriteInstId(context, ref value, out var instId))
            {
                HandleUnmanagedStruct(out RefHeader* header);
                var index = buffer->Index;
                context->IncreaseDepth();
                WriteUInt16((UInt16)value.Count);
                bool isKeyUnmanaged = !RuntimeHelpers.IsReferenceOrContainsReferences<TKey>();
                bool isValueUnmanaged = !RuntimeHelpers.IsReferenceOrContainsReferences<TValue>();
                foreach (var kv in value)
                {
                    var k = kv.Key;
                    if (isKeyUnmanaged)
                        WriteUnmanagedStruct(context, ref k); 
                    else
                        WriteRealTypeValue<TKey>(context, ref k);
                    
                    var v = kv.Value;
                    if (isValueUnmanaged)
                        WriteUnmanagedStruct(context, ref v);
                    else
                        WriteRealTypeValue<TValue>(context, ref v);
                }
                context->DecreaseDepth();
                header->InstId = (int)instId;
                header->Size = buffer->Index - index;
            }
        }
    }
}
